/*
 * gui.c
 */

#include <stdlib.h>
#include <utils/error.h>
#include <utils/png_utils.h>
#include "gui.h"
#include "gui_lua.h"
#include <agent/player.h>

#define TEXTURE_ARRAY_SIZE 20

/*
 * Some useful color macros
 */
GLfloat gui_color_blue[] = {0.0f, 0.0f, 1.0f, 1.0f};
GLfloat gui_color_red[] = {1.0f, 0.0f, 1.0f, 1.0f};
GLfloat gui_color_green[] = {0.0f, 1.0f, 0.0f, 1.0f};
GLfloat gui_color_darkgrey[] = {0.1f, 0.1f, 0.1f, 0.8f};
GLfloat gui_color_dullyellow[] = {0.3f, 0.3f, 0.0f, 0.8f};

/*
 * Viewport is needed to be polled to find window dimensions.
 */
GLint vPort[4];

/*
 * All loaded textures are put into this list so we can track their dimensions,
 * as well as abstract away some of the unnecessary details from the user.
 */
struct gui_texture *texture_list;
int texture_list_size;
int texture_list_init = 0;

/*
 * When loading PNGs we can get the width and height from the image file (which
 * shouldn't be surprising), so we can optionally load textures and store the
 * id and dimensions in a struct, which will allow the UI script to work with
 * textures without needing to worry about dimensions.
 */
struct gui_texture {
    GLuint texture_id;
    GLsizei width;
    GLsizei height;
};

/*
 * Sets up OpenGL so we can draw straight 2D on top of the already rendered
 * graphics.
 * Code originally by Jesse Lawson in a post in www.gamedev.net
 * Enhancements by Vincent David from same source
 */
void glEnable2D() {
    GLint vPort[4];

    glGetIntegerv(GL_VIEWPORT, vPort);

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();

    /*glOrtho(0, vPort[2], 0, vPort[3], -1, 1);*/
    glOrtho(vPort[0], vPort[0]+vPort[2], vPort[1], vPort[1]+vPort[3], -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();

    /* Slight translation necessary for pixel-perfect positioning */
    glTranslatef(0.375, 0.375, 0.);

    glPushAttrib(GL_DEPTH_BUFFER_BIT);
    glDisable(GL_DEPTH_TEST);
}

/*
 * Reverts OpenGL to state before glEnable2D was called.
 * Code originally by Dwarf with Axe (Jesse Lawson) in a post in www.gamedev.net
 */
void glDisable2D() {
    glPopAttrib();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();   
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();       
}

/*
 * Prints the given null-terminated character string on screen in the given x/y
 * location using the given font.
 * Available fonts are:
 * GLUT_BITMAP_8_BY_13 GLUT_BITMAP_9_BY_15
 * GLUT_BITMAP_TIMES_ROMAN_10 GLUT_BITMAP_TIMES_ROMAN_24
 * GLUT_BITMAP_HELVETICA_10 GLUT_BITMAP_HELVETICA_12 GLUT_BITMAP_HELVETICA_18
 */
void print_text( int x, int y, const char *string, void *font ) {
    glRasterPos2f(x, y);
    if( *string != 0 ) { /* test for non-empty string */
        do {
            glutBitmapCharacter(font, *string);
        } while( *(++string) );
    }
}

/*
 * Draws the texture represented by the given texture id at the given x/y
 * location.
 * NOTE: The texture id is the int returned by load_png, not the GLint of the
 * actual texture loaded.
 */
void gui_draw_texture( int x, int y, int texture ) {
    struct gui_texture tex_info;
    if( texture_list_init ) {
        if( texture < texture_list_size ) {
            tex_info = texture_list[texture];
            glEnable(GL_TEXTURE_2D);
            glBindTexture(GL_TEXTURE_2D, tex_info.texture_id);

            glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
            glBegin(GL_QUADS);
                glTexCoord2f( 0.0f, 0.0f );
                glVertex2f( x, y );

                glTexCoord2f( 1.0f, 0.0f );
                glVertex2f( x + tex_info.width, y );

                glTexCoord2f( 1.0f, 1.0f );
                glVertex2f( x + tex_info.width, y + tex_info.height );

                glTexCoord2f( 0.0f, 1.0f );
                glVertex2f( x, y + tex_info.height );
            glEnd();

            glDisable(GL_TEXTURE_2D);
        }
        else {
            printerr( ERROR_GUI_NOTEXTURE );
        }
    }
    else {
        printerr( ERROR_GUI_NOTEXINIT );
    }
}

/*
 * Draws the given texture at the given x/y location with the given width and
 * height.
 * NOTE: This function has been deprecated in favor of gui_draw_texture, but is
 * left in for historic reasons.
 */
/* TODO: better func name */
void gui_draw_texture2( int x, int y, int width, int height, GLuint texture ) {
    if( texture_list_init ) {
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, texture);

        glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
        glBegin(GL_QUADS);
            glTexCoord2f( 0.0f, 0.0f );
            glVertex2f( x, y );

            glTexCoord2f( 1.0f, 0.0f );
            glVertex2f( x + width, y );

            glTexCoord2f( 1.0f, 1.0f );
            glVertex2f( x + width, y + height );

            glTexCoord2f( 0.0f, 1.0f );
            glVertex2f( x, y + height );
        glEnd();

        glDisable(GL_TEXTURE_2D);
    }
    else {
        printerr( ERROR_GUI_NOTEXINIT );
    }
}

/*
 * Draws a health bar with the given dimensions at the given location. The HP
 * values are needed only to figure out how full the health bar should be,
 * values should at minimum be at a ratio roughly equivalent to the actual
 * values.
 */
void gui_draw_healthbar( int x, int y, int width, int height, int hp, int max ){
    int bar_value;
    /* if x/y is negative, it's pixels from the other side. */
    if( x < 0 ) {
        x += vPort[2] - width + 1;
    }
    if( y < 0 ) {
        y += vPort[3] - height + 1;
    }
    if( hp == max ) {
        /* Full HP bar, draw the bar and call it a day */
        glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
        glBegin(GL_QUADS);
            glVertex2f( x, y );
            glVertex2f( x, y + height );
            glVertex2f( x + width, y + height );
            glVertex2f( x + width, y );
        glEnd();
        glColor4f(1.0f, 0.5f, 0.5f, 1.0f);
        glBegin(GL_LINE_STRIP);
            glVertex2f( x + width - 2, y + height - 1 );
            glVertex2f( x, y + height - 1 );
            glVertex2f( x, y );
        glEnd();
        glColor4f(0.5f, 0.0f, 0.0f, 1.0f);
        glBegin(GL_LINE_STRIP);
            glVertex2f( x + 1, y );
            glVertex2f( x + width - 1, y );
            glVertex2f( x + width - 1, y + height - 1 );
        glEnd();
    }
    else if( hp <= 0 ) {
        /* Dead, so draw empty empty bar and call it a day */
        glColor4f(0.3f, 0.3f, 0.3f, 1.0f);
        glBegin(GL_QUADS);
            glVertex2f( x, y );
            glVertex2f( x, y + height );
            glVertex2f( x + width, y + height );
            glVertex2f( x + width, y );
        glEnd();
        glColor4f(0.1f, 0.1f, 0.1f, 1.0f);
        glBegin(GL_LINE_STRIP);
            glVertex2f( x + width - 2, y + height - 1 );
            glVertex2f( x, y + height - 1 );
            glVertex2f( x, y );
        glEnd();
        glColor4f(0.5f, 0.5f, 0.5f, 1.0f);
        glBegin(GL_LINE_STRIP);
            glVertex2f( x + 1, y );
            glVertex2f( x + width - 1, y );
            glVertex2f( x + width - 1, y + height - 1 );
        glEnd();
    }
    else {
        /* Beaten but not dead yet. Always gotta make me work harder... */
        bar_value = (int)((float)hp / (float)max * (float)width);
        glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
        glBegin(GL_QUADS);
            glVertex2f( x, y );
            glVertex2f( x + bar_value, y );
            glVertex2f( x + bar_value, y + height );
            glVertex2f( x, y + height );
        glEnd();
        glColor4f(1.0f, 0.5f, 0.5f, 1.0f);
        glBegin(GL_LINE_STRIP);
            glVertex2f( x + bar_value - 2, y + height - 1 );
            glVertex2f( x, y + height - 1);
            glVertex2f( x, y );
        glEnd();
        glColor4f(0.5f, 0.0f, 0.0f, 1.0f);
        glBegin(GL_LINE_STRIP);
            glVertex2f( x + 1, y );
            glVertex2f( x + bar_value - 1, y );
            glVertex2f( x + bar_value - 1, y + height - 1 );
        glEnd();
        glColor4f(0.3f, 0.3f, 0.3f, 1.0f);
        glBegin(GL_QUADS);
            glVertex2f( x + bar_value, y );
            glVertex2f( x + width, y );
            glVertex2f( x + width, y + height );
            glVertex2f( x + bar_value, y + height );
        glEnd();
        glColor4f(0.1f, 0.1f, 0.1f, 1.0f);
        glBegin(GL_LINE_STRIP);
            glVertex2f( x + width - 2, y + height - 1 );
            glVertex2f( x + bar_value - 1, y + height - 1 );
            /*glVertex2f( x + bar_value, y );*/
        glEnd();
        glColor4f(0.5f, 0.5f, 0.5f, 1.0f);
        glBegin(GL_LINE_STRIP);
            glVertex2f( x + bar_value + 1, y );
            glVertex2f( x + width - 1, y );
            glVertex2f( x + width - 1, y + height - 1);
        glEnd();
    }
}

/*
 * Draws the given string at the given location with the given width, height,
 * and colors in a Times New Roman 24pt font.
 * NOTE: The width and height refer only to the dimensions of the box.
 */
void gui_draw_big_textbox( int x, int y, int width, int height,
    const GLfloat box_color[], const GLfloat trim_color[],
    const GLfloat font_color[], const char *string ) {
    /* if x/y is negative, it's pixels from the other side. */
    if( x < 0 ) {
        x += vPort[2] - width + 1;
    }
    if( y < 0 ) {
        y += vPort[3] - height + 1;
    }
    glColor4f(box_color[0], box_color[1], box_color[2], box_color[3]);
    glBegin(GL_QUADS);
        glVertex2f( x, y );
        glVertex2f( x, y + height );
        glVertex2f( x + width, y + height );
        glVertex2f( x + width, y );
    glEnd();
    /* Next we draw the trim */
    glColor4f(trim_color[0], trim_color[1], trim_color[2], trim_color[3]);
    glBegin(GL_LINE_LOOP);
        glVertex2f( x + 1, y + 1 );
        glVertex2f( x + 1, y + height - 2 );
        glVertex2f( x + width - 2, y + height - 2);
        glVertex2f( x + width - 2, y + 1 );
    glEnd();
    /* Finally, the text */
    glColor4f(font_color[0], font_color[1], font_color[2], font_color[3]);
    print_text( x + 5, y + 8, string, GLUT_BITMAP_TIMES_ROMAN_24 );
}

/*
 * Draws the given string at the given location with the given width, height,
 * and colors in a Helvetica 12pt font.
 * NOTE: The width and height refer only to the dimensions of the box.
 */
void gui_draw_textbox( int x, int y, int width, int height,
    const GLfloat box_color[], const GLfloat trim_color[],
    const GLfloat font_color[], const char *string ) {
    /* If x/y is negative, it's pixels from the other side. */
    if( x < 0 ) {
        x += vPort[2] - width + 1;
    }
    if( y < 0 ) {
        y += vPort[3] - height + 1;
    }
    /* First we draw the container */
    glColor4f(box_color[0], box_color[1], box_color[2], box_color[3]);
    glBegin(GL_QUADS);
        glVertex2f( x, y );
        glVertex2f( x, y + height );
        glVertex2f( x + width, y + height );
        glVertex2f( x + width, y );
    glEnd();
    /* Next we draw the trim */
    glColor4f(trim_color[0], trim_color[1], trim_color[2], trim_color[3]);
    glBegin(GL_LINE_LOOP);
        glVertex2f( x + 1, y + 1);
        glVertex2f( x + 1, y + height - 2 );
        glVertex2f( x + width - 2, y + height - 2);
        glVertex2f( x + width - 2, y + 1 );
    glEnd();
    /* Finally, the text */
    glColor4f(font_color[0], font_color[1], font_color[2], font_color[3]);
    print_text( x + 5, y + 6, string, GLUT_BITMAP_HELVETICA_12 );
}

/*
 * Initializes the texture array used to store necessary information about
 * images loaded through load_png.
 * NOTE: This function should not be called directly!
 */
void initialize_texture_array() {
    if( !texture_list_init ) {
        texture_list = (struct gui_texture*)malloc(sizeof(struct gui_texture) *
            TEXTURE_ARRAY_SIZE);
        texture_list_size = 0;
        texture_list_init = 1;
    }
    else {
        printerr( WARNING_GUI_REINITTEX );
    }
}

/*
 * Loads the PNG file located at the given file name, returning an int
 * identifying the PNG in the PNG texture array.
 */
int load_png(const char *filename) {
    struct gui_texture new_texture;
    if( !texture_list_init ) {
        initialize_texture_array();
    }
    new_texture.texture_id = loadPNGTextureWH( filename, &(new_texture.width),
        &(new_texture.height) );
    texture_list[texture_list_size] = new_texture;
    texture_list_size++;
    return texture_list_size - 1;
}

/*
 * Draws the Graphical User Interface.
 */
void drawGUI() {
    glGetIntegerv(GL_VIEWPORT, vPort);
    if( lua_gui_is_initialized() ) {
        glEnable2D();
            lua_gui_run();
        glDisable2D();
    }
}
